package transport;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;

import network.TPA;

public class ControlConnection extends Thread {

	Transport transport;
	Socket socket;
	BufferedReader in;
	BufferedWriter out;
	TransportConnection connection;
	boolean disconnected;

	public ControlConnection(Socket s, Transport t) throws IOException {
		this.transport = t;
		this.socket = s;
		this.in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		this.out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		disconnected = false;
		this.start();
	}
	/**
	 * The run method. It reads incoming messages from the socket and sends those to handleInput
	 */
	public void run() {
		//TODO niet een while true, maar moet ook gesloten kunnen worden
		while (!disconnected) {
			try {try{
				String input = in.readLine();
				handleInput(input);
			}catch(SocketException e){
				System.out.print("Tried to read, but Socket was closed");
				disconnected = true;
			}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	/**
	 * Method that handles input that comes from the socket. It handles the input according to the internal protocol
	 * @param input The string representation of the input from the socket
	 * @require input != null
	 */
	public void handleInput(String input){
		String[] arguments = input.split(" ");
		if(arguments[0].equals(Constants.CONNECTING)){
			connecting(arguments);
		}
		if (arguments[0].equals(Constants.DISCONNECT)){
			disconnect();
		}
		if (arguments[0].equals(Constants.LISTEN)){
			listen(arguments[1]);
		}
	}
	/**
	 * Handles input if it started with the connecting command. Sets up an extern connection to the machine it was asked to by the application
	 * @param arguments
	 * @require arguments[0].equals(Constants.CONNECTING)
	 * @require arguments == 3
	 */
	public void connecting(String[] arguments){
		TPA ownTPA = transport.getNetwork().getOwnAdress();
		TPA externTPA = new TPA(Integer.parseInt(arguments[2]));
		byte outPort = (byte) Integer.parseInt(arguments[1]);
		ArrayList<TransportConnection> connections = transport.getConnections();
		boolean portFound = false;
		int inPort = -1;
		for (int j = 0; j <= Constants.PORT_MAX && !portFound; j++){
			portFound = true;
			for (int i = 0; i < connections.size(); i++){
				if (connections.get(i).getInPort() == j && connections.get(i).getOutgoingAdresst().getAdress() == externTPA.getAdress()){
					portFound = false;
				}
			}
			if (portFound){
				inPort = j;
			}
		}
		
		this.connection = new TransportConnection(ownTPA, externTPA, (byte)inPort, outPort, transport, this);
		transport.getTable().add(connection);
		try {
			connection.sendsyn();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
	}
	/**
	 * Method that should be called by the TransportConnection after a syn/ack has been received
	 * it sends to the application that the connection has been set up and a dataconnection can be made
	 */
	public void connected(){
		boolean finished = false;
		ServerSocket server = null;
		while (!finished){
			try {
				int port = (int) ((Math.random() *16382) + 49152);
				server = new ServerSocket(port);
				send(Constants.CONNECTED + " " + port + '\n');
				finished = true;
			} catch (IOException e) {
			}
		}
		try {
			Socket socket = server.accept();
			connection.setDataSocket(socket);
			connection.start();
			connection.connected();
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * Is called if the application wants to disconnect. Tells the transport layer that it wants to disconnect
	 */
	public void disconnect(){
		try {
			transport.quit(connection, connection.getOutgoingAdresst());
			disconnected = true;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void listen(String arguments){
		TPA ownTPA = transport.getNetwork().getOwnAdress();
		byte inPort = (byte) Integer.parseInt(arguments);
		ListeningConnection l = new ListeningConnection(inPort, ownTPA, this);
		transport.addListener(l);
	}
	/**
	 * Sends a message to the application, it ends the message with an endline
	 * @param msg the message to be sent
	 */
	public void send(String msg){
		try {
			out.write(msg + '\n');
			out.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public Transport getTransport(){
		return this.transport;
	}
	public void setConnection(TransportConnection connection){
		this.connection = connection;
	}
	public TransportConnection getConnection(){
		return this.connection;
	}
	
	public void disconnected(){
		disconnected = true;
		send(Constants.DISCONNECTED);
		
	}

}
